<?php
namespace PhpBoot\Lock;

use PhpBoot\Utils\SafeFileWriter;

class FileLock implements LockInterface
{
    private $file;
    private $locked = false;
    /**
     * @var string
     */
    private $directory;
    /**
     * @var int
     */
    private $directoryStringLength;
    /**
     * @var bool
     */
    private $isRunningOnWindows;


    public function __construct()
    {
        $this->directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'lock';
        $this->directoryStringLength = strlen($this->directory);
        $this->isRunningOnWindows    = defined('PHP_WINDOWS_VERSION_BUILD');
    }
    public function lock($key, $ttl)
    {
        if($this->locked){
            \PhpBoot\abort("relock $key");
        }
        $filename = $this->getFilename($key);

        if (! $this->createPathIfNeeded(pathinfo($filename, PATHINFO_DIRNAME))) {
            \PhpBoot\abort("unable to create dir $filename");
        }
        $this->file = @fopen($filename, 'a');
        if (!$this->file || !flock($this->file, LOCK_EX | LOCK_NB)) {
            if($this->file){
                fclose($this->file);
            }
            return false;
        } else {
            $this->locked = true;
        }
        return true;
    }

    public function unlock($key)
    {
        $this->locked or \PhpBoot\abort("unlock unlocked $key");
        flock($this->file, LOCK_UN);
        fclose($this->file);
        $this->file = null;
        $this->locked = false;
        return true;
    }
    private function getFilename($id)
    {
        $hash = hash('sha256', $id);

        // This ensures that the filename is unique and that there are no invalid chars in it.
        if (
            $id === ''
            || (strlen($id) * 2 > 255)
            || ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2) > 258)
        ) {
            // Most filesystems have a limit of 255 chars for each path component. On Windows the the whole path is limited
            // to 260 chars (including terminating null char). Using long UNC ("\\?\" prefix) does not work with the PHP API.
            // And there is a bug in PHP (https://bugs.php.net/bug.php?id=70943) with path lengths of 259.
            // So if the id in hex representation would surpass the limit, we use the hash instead. The prefix prevents
            // collisions between the hash and bin2hex.
            $filename = '_' . $hash;
        } else {
            $filename = bin2hex($id);
        }

        return $this->directory
            . DIRECTORY_SEPARATOR
            . substr($hash, 0, 2)
            . DIRECTORY_SEPARATOR
            . $filename;
    }
    /**
     * Create path if needed.
     *
     * @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
     */
    private function createPathIfNeeded($path)
    {
        if (! is_dir($path)) {
            if (@mkdir($path, 0777 & (~0002), true) === false && ! is_dir($path)) {
                return false;
            }
        }
        return true;
    }
}